//package com.geely.pma.climate.api
package com.wanghaibo.vgashow.api;

import android.content.Context
import android.content.Intent
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import com.ecarx.xui.adaptapi.FunctionStatus
import com.ecarx.xui.adaptapi.binder.IConnectable
import com.ecarx.xui.adaptapi.binder.IConnectable.IConnectWatcher
import com.ecarx.xui.adaptapi.car.Car
import com.ecarx.xui.adaptapi.car.ICar
import com.ecarx.xui.adaptapi.car.base.ICarFunction
import com.ecarx.xui.adaptapi.car.base.ICarFunction.IFunctionValueWatcher
//import com.geely.pma.climate.api.observation.CarFunctionCustomizeObservation
//import com.geely.pma.climate.api.observation.CarFunctionObservationManager
//import com.geely.pma.climate.api.observation.CarFunctionSupportedStatusObservation
//import com.geely.pma.climate.api.observation.CarFunctionValueObservation
//import com.geely.pma.climate.api.single.*
import com.wanghaibo.vgashow.api.observation.CarFunctionCustomizeObservation
import com.wanghaibo.vgashow.api.observation.CarFunctionObservationManager
import com.wanghaibo.vgashow.api.observation.CarFunctionSupportedStatusObservation
import com.wanghaibo.vgashow.api.observation.CarFunctionValueObservation
import com.wanghaibo.vgashow.api.single.*
import io.reactivex.Single
import io.reactivex.SingleEmitter
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicBoolean

/**
 * Name: CarAdaptAPI
 *
 * Created by Wuhao on 2019/12/9
 *
 * Version: 0.0.1
 */
class CarAdaptAPI private constructor() : ICarAdaptAPI {
    @Volatile
    private var actualCar: ICar? = null

    // initFlag is true if initializing or initialized already
    private val initFlag = AtomicBoolean(false)

    @Volatile
    private var carFunction: ICarFunction? = null

    @Volatile
    private var initTask: Disposable? = null
    private val pendingOperations: Queue<ICarFunctionOperation>
    private val observationManagerMap: MutableMap<Int?, CarFunctionObservationManager>

    init {
        pendingOperations = LinkedList()
        observationManagerMap = ConcurrentHashMap()
    }

    @Synchronized
    override fun init(context: Context) {
        if (!initFlag.compareAndSet(false, true)) {
            return
        }
        initTask = Single.create { carEmitter: SingleEmitter<ICar> ->
            Log.i(TAG, "Fetching ICar...")
                val car = Car.create(context)
            if (car != null) {
                carEmitter.onSuccess(car)
            } else {
                carEmitter.onError(
                    IllegalStateException("ICar returned by Car.create(Context) cannot be null")
                )
            }
        }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).flatMap { car ->
            Single.create { carEmitter: SingleEmitter<ICar> ->
                actualCar = car
                if (car is IConnectable) {
                    (car as IConnectable).apply {
                        Log.i(TAG, "Start registerConnectWatcher")
                        registerConnectWatcher(object : IConnectWatcher {
                            override fun onConnected() {
                                Log.i(TAG, "RegisterConnectWatcher onConnected.")
                                carEmitter.onSuccess(car)
                            }

                            override fun onDisConnected() {
                                Log.i(TAG, "RegisterConnectWatcher onDisConnected.")
                                // reconnect when car service killed
                                if (actualCar == car) {
                                    destroy(false)
                                    init(context)
                                }
                            }
                        })
                        connect()
                    }
                } else {
                    carEmitter.onSuccess(car)
                }
            }
        }.observeOn(AndroidSchedulers.mainThread()).flatMap { car ->
            Single.create { carFunctionEmitter: SingleEmitter<ICarFunction> ->
                Log.i(TAG, "Fetching ICarFunction...")
                val function = car.iCarFunction
                if (function != null) {
                    carFunctionEmitter.onSuccess(function)
                } else {
                    carFunctionEmitter.onError(
                        IllegalStateException(
                            "ICarFunction returned by ICar.getICarFunction() cannot be null."
                        )
                    )
                }
            }
        }.timeout(5, TimeUnit.SECONDS, Single.error<ICarFunction>(TimeoutException("5 seconds")))
            .subscribe(
                { function ->
                    Log.i(TAG, "onSuccess action")
                    onCarFunctionInitialized(function)
                    initTask = null
                },
                { throwable ->
                    throwable.printStackTrace()
                    onCarFunctionInitFailed(throwable)
                    context.sendBroadcast(Intent(INIT_FAILURE))
                    initTask = null
                    destroy()
                })
    }

    @Synchronized
    override fun destroy(clear: Boolean) {
        // clear initFlag
        if (!initFlag.compareAndSet(true, false)) {
            return
        }

        // clear underlying connection
        if (actualCar is IConnectable) {
            (actualCar as IConnectable).apply {
                unregisterConnectWatcher()
                if (clear) {
                    // don't disconnect when car service killed
                    disconnect()
                }
            }
        }
        actualCar = null

        // clear init resources
        initTask?.apply {
            initTask = null
            dispose()
            synchronized(pendingOperations) {
                pendingOperations.clear()
            }
        }

        // clear observation resources
        carFunction?.also { function ->
            carFunction = null
            if (clear) {
                observationManagerMap.values.forEach {
                    it.clear(function)
                }
                observationManagerMap.clear()
            }
        }
    }

    private fun onCarFunctionInitialized(function: ICarFunction) {
        if (carFunction != null) {
            // already initialized
            return
        }
        synchronized(pendingOperations) {
            if (carFunction != null) {
                return
            }
            // Use CarFunctionProxy to log communication between app and AdaptAPI
            val carFunctionProxy: ICarFunction = CarFunctionProxy(function)
            Log.i(TAG, "ICarFunction initialized.")
            // register again if reconnected
            for (manager in observationManagerMap.values) {
                manager.forceRegister(carFunctionProxy)
            }
            pendingOperations.forEach {
                it.onInitialized(carFunctionProxy)
            }
            pendingOperations.clear()
            carFunction = carFunctionProxy
        }
    }

    private fun onCarFunctionInitFailed(throwable: Throwable) {
        synchronized(pendingOperations) {
            pendingOperations.forEach {
                it.onInitFailed(throwable)
            }
            pendingOperations.clear()
        }
    }

    private fun tryOperate(operation: ICarFunctionOperation) {
        carFunction?.also {
            operation.onInitialized(it)
            return
        }
        synchronized(pendingOperations) {
            carFunction?.also {
                operation.onInitialized(it)
                return
            }
            pendingOperations.offer(operation)
        }
    }

    override fun getSupportedFunctionValue(function: Int): Single<IntArray> {
        if (!initFlag.get()) {
            return Single.error(IllegalStateException("ICarAdaptAPI not initialized yet."))
        }
        return Single.create { emitter: SingleEmitter<IntArray> ->
            tryOperate(GetSupportedFunctionValueTask(emitter, function))
        }
    }

    override fun isFunctionSupported(function: Int, zone: Int?): Single<FunctionStatus> {
        if (!initFlag.get()) {
            return Single.error(IllegalStateException("ICarAdaptAPI not initialized yet."))
        }
        return Single.create { emitter: SingleEmitter<FunctionStatus> ->
            tryOperate(IsFunctionSupportedTask(emitter, function, zone))
        }
    }

    override fun setFunctionValue(function: Int, zone: Int?, funcValue: Int): Single<Boolean> {
        if (!initFlag.get()) {
            return Single.error(IllegalStateException("ICarAdaptAPI not initialized yet."))
        }
        return Single.create { emitter: SingleEmitter<Boolean> ->
            tryOperate(SetFunctionValueTask(emitter, function, zone, funcValue))
        }
    }

    override fun setCustomizeFunctionValue(
        function: Int, zone: Int?, value: Float
    ): Single<Boolean> {
        if (!initFlag.get()) {
            return Single.error(IllegalStateException("ICarAdaptAPI not initialized yet."))
        }
        return Single.create { emitter: SingleEmitter<Boolean> ->
            tryOperate(SetCustomizeFunctionValueTask(emitter, function, zone, value))
        }
    }

    override fun getFunctionValue(function: Int, zone: Int?): Single<Int> {
        if (!initFlag.get()) {
            return Single.error(IllegalStateException("ICarAdaptAPI not initialized yet."))
        }
        return Single.create { emitter: SingleEmitter<Int> ->
            tryOperate(GetFunctionValueTask(emitter, function, zone))
        }
    }

    override fun getCustomizeFunctionValue(function: Int, zone: Int?): Single<Float> {
        if (!initFlag.get()) {
            return Single.error(IllegalStateException("ICarAdaptAPI not initialized yet."))
        }
        return Single.create { emitter: SingleEmitter<Float> ->
            tryOperate(GetCustomizeFunctionValueTask(emitter, function, zone))
        }
    }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun observeCarFunctionValue(
        watcher: IFunctionValueWatcher, function: Int?, vararg zones: Int
    ): Boolean {
        if (!initFlag.get()) {
            return false
        }
        val manager =
            observationManagerMap.computeIfAbsent(function, ::CarFunctionObservationManager)
        tryOperate(CarFunctionValueObservation(function, zones, watcher, manager))
        return true
    }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun observeCarFunctionCustomize(
        watcher: IFunctionValueWatcher, function: Int?, vararg zones: Int
    ): Boolean {
        if (!initFlag.get()) {
            return false
        }
        val manager =
            observationManagerMap.computeIfAbsent(function, ::CarFunctionObservationManager)
        tryOperate(CarFunctionCustomizeObservation(function, zones, watcher, manager))
        return true
    }

    @RequiresApi(Build.VERSION_CODES.N)
    override fun observeSupportedFunctionStatus(
        watcher: IFunctionValueWatcher, function: Int?, vararg zones: Int
    ): Boolean {
        if (!initFlag.get()) {
            return false
        }
        val manager =
            observationManagerMap.computeIfAbsent(function, ::CarFunctionObservationManager)
        tryOperate(CarFunctionSupportedStatusObservation(function, zones, watcher, manager))
        return true
    }

    override fun stopObservation(watcher: IFunctionValueWatcher) {
        if (!initFlag.get()) {
            return
        }
        for (manager in observationManagerMap.values) {
            carFunction?.also {
                manager.removeIFunctionValueWatcher(it, watcher)
            }
        }
    }

    companion object {
        private const val TAG = "CarAdaptAPI"
        //
        const val INIT_FAILURE = "com.geely.pma.climate.api.CarAdaptAPI.INIT_FAILURE"

        val INSTANCE: ICarAdaptAPI by lazy { CarAdaptAPI() }
    }
}